package com.grupoconstruobras.sigo.general.reporte;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRResultSetDataSource;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;

import org.apache.log4j.Logger;
//FIXME: import org.jboss.solder.el.Expressions;

public abstract class JasperReportGenerator {

	protected String filepath;
	protected Connection connection;
	protected List<String> options = new ArrayList<String>();
	protected String field;
	protected Map<String, Object> parameters = new HashMap<String, Object>();

	protected JasperReport report;
	protected ResultSet resultSet;
	protected String name;

	protected Logger logger;

	/*FIXME: @Inject
	protected Expressions expressions;*/

	public void setIdUser(String idUser) {
		addParameter("usuario", idUser);
	}

	protected void setField(String field) {
		this.field = field;
	}

	public void setFilepath(String filepath) {
		this.filepath = filepath;
	}

	public void setConnection(Connection connection) {
		this.connection = connection;
	}

	public void setOptions(List<String> options) {
		this.options = options;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void generarReporte() throws JRException {
		if (filepath == null) {
			throw new RuntimeException(
					"Estado ilegal, primero se debe llamar setFilepath(String)");
		}
		report = JasperCompileManager.compileReport(filepath);
	}

	protected abstract String getModifiedQuery(String query);

	protected abstract String addConditions(String query);

	protected String addConditions(String query, String field, String idField) {
		int indexToBeginSearch = query.indexOf(field.substring(1));
		final String TRUE_KEYWORD = "true";
		int keywordIndex = query.indexOf(TRUE_KEYWORD, indexToBeginSearch);
		boolean filter = true;
		if (keywordIndex == -1) {
			logger.warn("La palabra clave \"true\" para hacer la modificación está ausente del query original, se retorna el query original.");
			filter = false;
		}
		if (!filter) {
			return query;
		}
		String firstSubString = query.substring(0, keywordIndex);
		StringBuilder firstHalf = new StringBuilder(firstSubString);
		String secondSubstring = query.substring(keywordIndex
				+ TRUE_KEYWORD.length());
		StringBuilder secondHalf = new StringBuilder(secondSubstring);
		StringBuilder whereClause = new StringBuilder();
		String option = String.format("%s = \'%s\'", field, idField);
		whereClause.append(option);
		String newQuery = firstHalf.append(whereClause).append(secondHalf)
				.toString();
		return newQuery;
	}

	public void generarResultSet() throws SQLException {
		if (report == null) {
			throw new RuntimeException(
					"Estado ilegal, primero se debe llamar generarReporte()");
		}
		if (connection == null) {
			throw new RuntimeException(
					"Estado ilegal, primero se debe llamar setConnection(Connection)");
		}
		String query = report.getQuery().getText();
		query = getModifiedQuery(query);
		query = addConditions(query);
		logger.info("Query generado: " + query);
		PreparedStatement statement = connection.prepareCall(query);
		resultSet = statement.executeQuery();
	}

	public boolean estaElResultSetVacio() throws SQLException {
		if (resultSet == null) {
			throw new RuntimeException(
					"Estado ilegal, primero se debe llamar generarResultSet()");
		}
		return !resultSet.isBeforeFirst();
	}

	protected Map<String, Object> getParameters() {
		return parameters;
	}

	public JasperPrint obtenerPrint() throws JRException {
		Map<String, Object> params = getParameters(); 
		params.put(JRParameter.REPORT_DATA_SOURCE, resultSet);
	    params.put(JRParameter.REPORT_CONNECTION, connection);
		JasperPrint print = JasperFillManager.fillReport(report, params,
				new JRResultSetDataSource(resultSet));
		return print;
	}

	public JasperPrint obtenerPrint(Connection connection) throws JRException {
		Map<String, Object> params = getParameters();
		JasperPrint print = JasperFillManager.fillReport(report, params,
				connection);
		return print;
	}

	private static class SimpleJasperReportGenerator extends
			JasperReportGenerator {
		public SimpleJasperReportGenerator() {
			logger = Logger.getLogger(SimpleJasperReportGenerator.class);
		}

		@Override
		protected String getModifiedQuery(String query) {
			return query;
		}

		@Override
		protected String addConditions(String query) {
			return query;
		}

	}

	private static class JasperReportGeneratorWithModifiedQuery extends
			JasperReportGenerator {
		public JasperReportGeneratorWithModifiedQuery() {
			logger = Logger
					.getLogger(JasperReportGeneratorWithModifiedQuery.class);
		}

		@Override
		protected String getModifiedQuery(String query) {
			if (options == null) {
				throw new RuntimeException(
						"Estado ilegal, primero se debe llamar setOptions(List<String>)");
			}
			if (field == null) {
				throw new RuntimeException(
						"Estado ilegal, primero se debe llamar setField(String)");
			}
			final String TRUE_KEYWORD = "true";
			int keywordIndex = query.indexOf(TRUE_KEYWORD);
			boolean filter = true;
			if (keywordIndex == -1) {
				logger.warn("La palabra clave \"true\" para hacer la modificación está ausente del query original, se retorna el query original.");
				filter = false;
			}
			if (options.size() == 0) {
				logger.warn("El atributo options está vacío, se retorna el query original.");
				filter = false;
			}
			if (!filter) {
				return query;
			}
			String firstSubString = query.substring(0, keywordIndex);
			StringBuilder firstHalf = new StringBuilder(firstSubString);
			String secondSubstring = query.substring(keywordIndex
					+ TRUE_KEYWORD.length());
			StringBuilder secondHalf = new StringBuilder(secondSubstring);
			StringBuilder whereClause = new StringBuilder();
			int totalRecords = options.size();
			String whereFormat = field + " = \'%s\'";
			for (int i = 0; i < totalRecords - 1; ++i) {
				String option = String.format(whereFormat, options.get(i));
				whereClause.append(option).append(" OR ");
			}
			String option = String.format(whereFormat,
					options.get(totalRecords - 1));
			whereClause.append(option);
			return firstHalf.append(whereClause).append(secondHalf).toString();
		}

		@Override
		protected String addConditions(String query) {
			String[] keys = { "idMunicipio", "idDepartamento", "idPais" };
			for (String key : keys) {
				String parameter = (String) parameters.get(key);
				if (parameter != null && !parameter.isEmpty()) {
					String keyWithoutID = key.substring(2).toLowerCase();
					query = addConditions(
							query,
							String.format("%s.id%s", keyWithoutID.charAt(0)
									+ "", keyWithoutID), parameter);
				}
			}
			return query;
		}
	}

	public static JasperReportGenerator getInstance(String reportName) {
		JasperReportGenerator generator = null;
		if (reportName.equals("resumen_bienes_por_tipo")
				|| reportName.equals("reporte_general_bien")
				|| reportName.equals("reporte_bienes_entregados")) {
			generator = new SimpleJasperReportGenerator();
		} else if (reportName.equals("reporte_por_tipo")
				|| reportName.equals("reporte_bienes_registrados")) {
			generator = new JasperReportGeneratorWithModifiedQuery();
			generator.setField("t.tp_bien");
		} else {
			throw new AssertionError("Nombre de reporte desconocido: "
					+ reportName);
		}
		generator.setName(reportName);
		return generator;
	}

	public void addOption(String option) {
		options.add(option);
	}

	public void addOptions(List<String> options) {
		this.options.addAll(options);
	}

	public void addParameters(Map<String, Object> parameters) {
		this.parameters.putAll(parameters);
	}

	public void addParameter(String key, Object value) {
		parameters.put(key, value);
	}
}
